# -*- coding: binary -*-

module Msf
  module Exploit::Remote::SMB::Server
    module Share
      module InformationLevel
        module Find

          # Handles a TRANS2_FIND_FIRST2 transaction request with SMB_FIND_FILE_BOTH_DIRECTORY_INFO
          # Information Level.
          #
          # @param c [Socket] The client sending the request.
          # @param path [String] The path which the client is requesting info from.
          # @return [Integer] The number of bytes returned to the client as response.
          def smb_cmd_find_file_both_directory_info(c, path)
            contents = get_file_contents(client: c)

            if path && path.include?(file_name.downcase)
              data = Rex::Text.to_unicode(file_name)
              length = contents.length
              ea = 0
              alloc = 1048576 # Allocation Size = 1048576 || 1Mb
              attrib = CONST::SMB_EXT_FILE_ATTR_NORMAL
              search = 1
            elsif path && folder_name && path.ends_with?(folder_name.downcase)
              data = Rex::Text.to_unicode(path)
              length = 0
              ea = 0x21
              alloc = 0 # 0Mb
              attrib = CONST::SMB_EXT_FILE_ATTR_DIRECTORY
              search = 0x100
            elsif path && path == "\\"
              data = Rex::Text.to_unicode(path)
              length = 0
              ea = 0x21
              alloc = 0 # 0Mb
              attrib = CONST::SMB_EXT_FILE_ATTR_DIRECTORY
              search = 0x100
            else
              return smb_error(CONST::SMB_COM_TRANSACTION2, c, CONST::SMB_STATUS_NO_SUCH_FILE, true)
            end

            send_find_file_both_directory_info_res(c, {
              data: data,
              end_of_file: length,
              ea_error_offset: ea,
              allocation_size: alloc,
              file_attributes: attrib,
              search_count: search,
              search_offset: search
            })
          end

          # Handles a TRANS2_FIND_FIRST2 transaction request with SMB_FIND_FILE_NAMES_INFO
          # Information Level.
          #
          # @param c [Socket] The client sending the request.
          # @param path [String] The path which the client is requesting info from.
          # @return [Integer] The number of bytes returned to the client as response.
          def smb_cmd_find_file_names_info(c, path)
            if path && path.ends_with?(file_name.downcase)
              data = Rex::Text.to_unicode(file_name)
            elsif path && folder_name && path.ends_with?(folder_name.downcase)
              data = Rex::Text.to_unicode(path)
            elsif path && path == "\\"
              data = Rex::Text.to_unicode(path)
            else
              return smb_error(CONST::SMB_COM_TRANSACTION2, c, CONST::SMB_STATUS_NO_SUCH_FILE, true)
            end

            send_find_file_names_info_res(c, { data: data })
          end

          # Handles a TRANS2_FIND_FIRST2 transaction request with SMB_FIND_FILE_FULL_DIRECTORY_INFO
          # Information Level.
          #
          # @param c [Socket] The client sending the request.
          # @param path [String] The path which the client is requesting info from.
          # @return [Integer] The number of bytes returned to the client as response.
          def smb_cmd_find_file_full_directory_info(c, path)
            contents = get_file_contents(client: c)

            if path && path.include?(file_name.downcase)
              data = Rex::Text.to_unicode(file_name)
              length = contents.length
              ea = 0
              alloc = 1048576 # Allocation Size = 1048576 || 1Mb
              attrib = CONST::SMB_EXT_FILE_ATTR_NORMAL # File
              search = 0x100
            elsif path && folder_name && path.ends_with?(folder_name.downcase)
              data = Rex::Text.to_unicode(path)
              length = 0
              ea = 0x21
              alloc = 0 # 0Mb
              attrib = CONST::SMB_EXT_FILE_ATTR_DIRECTORY
              search = 1
            elsif path && path == "\\"
              data = Rex::Text.to_unicode(path)
              length = 0
              ea = 0x21
              alloc = 0 # 0Mb
              attrib = CONST::SMB_EXT_FILE_ATTR_DIRECTORY
              search = 1
            else
              return smb_error(CONST::SMB_COM_TRANSACTION2, c, CONST::SMB_STATUS_NO_SUCH_FILE, true)
            end

            send_find_full_directory_info_res(c, {
              data: data,
              end_of_file: length,
              ea_error_offset: ea,
              allocation_size: alloc,
              file_attributes: attrib,
              search_count: search,
              search_offset: search
            })
          end

          # Builds and sends an TRANS2_FIND_FIRST2 response with SMB_FIND_FILE_BOTH_DIRECTORY_INFO
          # information level.
          #
          # @param c [Socket] The client to answer.
          # @param opts [Hash{Symbol => <Integer, String>}] Response custom values.
          # @option opts [Integer] :search_count The number of entries returned by the search.
          # @option opts [Integer] :end_of_search 0 if search continues or nonzero otherwise.
          # @option opts [Integer] :ea_error_offset should be 0 for SMB_FIND_FILE_BOTH_DIRECTORY_INFO.
          # @option opts [Integer] :end_of_file The byte offset to the end of the file.
          # @option opts [Integer] :allocation_size The file allocation size in bytes.
          # @option opts [Integer] :file_attributes The extended file attributes of the file.
          # @option opts [String] :data The long name of the file.
          # @return [Integer] The number of bytes returned to the client as response.
          def send_find_file_both_directory_info_res(c, opts = {})
            data = opts[:data] || ''
            search_count = opts[:search_count] || 0
            end_of_search = opts[:end_of_search] || 0
            ea_error_offset = opts[:ea_error_offset] || 0
            end_of_file = opts[:end_of_file] || 0
            allocation_size = opts[:allocation_size] || 0
            file_attributes = opts[:file_attributes] || 0

            pkt = CONST::SMB_TRANS_RES_PKT.make_struct
            smb_set_defaults(c, pkt)

            trans2_params = CONST::SMB_TRANS2_FIND_FIRST2_RES_PARAMETERS.make_struct
            trans2_params.v['SID'] = 0xfffd
            trans2_params.v['SearchCount'] = search_count
            trans2_params.v['EndOfSearch'] = end_of_search
            trans2_params.v['EaErrorOffset'] = ea_error_offset
            trans2_params.v['LastNameOffset'] = 0

            find_file = CONST::SMB_FIND_FILE_BOTH_DIRECTORY_INFO_HDR.make_struct
            find_file.v['NextEntryOffset'] = CONST::SMB_FIND_FILE_BOTH_DIRECTORY_INFO_HDR_LENGTH + data.length
            find_file.v['FileIndex'] = 0
            find_file.v['loCreationTime'] = lo
            find_file.v['hiCreationTime'] = hi
            find_file.v['loLastAccessTime'] = lo
            find_file.v['hiLastAccessTime'] = hi
            find_file.v['loLastWriteTime'] = lo
            find_file.v['hiLastWriteTime'] = hi
            find_file.v['loLastChangeTime'] = lo
            find_file.v['hiLastChangeTime'] = hi
            find_file.v['EndOfFile'] = end_of_file
            find_file.v['AllocationSize'] = allocation_size
            find_file.v['ExtFileAttributes'] = file_attributes
            find_file.v['FileName'] = data

            send_trans2_res(c, trans2_params, find_file)
          end

          # Builds and sends an TRANS2_FIND_FIRST2 response with SMB_FIND_FILE_NAMES_INFO
          # information level.
          # @param c [Socket] The client to answer.
          # @param opts [Hash{Symbol => <Integer, String>}] Response custom values.
          # @option opts [String] :data The long name of the file.
          # @return [Integer] The number of bytes returned to the client as response.
          def send_find_file_names_info_res(c, opts = {})
            data = opts[:data] || ''

            pkt = CONST::SMB_TRANS_RES_PKT.make_struct
            smb_set_defaults(c, pkt)

            find_file = CONST::SMB_FIND_FILE_NAMES_INFO_HDR.make_struct
            find_file.v['NextEntryOffset'] = CONST::SMB_FIND_FILE_NAMES_INFO_HDR_LENGTH + data.length
            find_file.v['FileIndex'] = 0
            find_file.v['FileName'] = data

            trans2_params = CONST::SMB_TRANS2_FIND_FIRST2_RES_PARAMETERS.make_struct
            trans2_params.v['SID'] = 0xfffd
            trans2_params.v['SearchCount'] = 1
            trans2_params.v['EndOfSearch'] = 1
            trans2_params.v['EaErrorOffset'] = 0
            trans2_params.v['LastNameOffset'] = 0

            send_trans2_res(c, trans2_params, find_file)
          end

          # Builds and sends an TRANS2_FIND_FIRST2 response with SMB_FIND_FILE_FULL_DIRECTORY_INFO
          # information level.
          #
          # @param c [Socket] The client to answer.
          # @param opts [Hash{Symbol => <Integer, String>}] Response custom values.
          # @option opts [Integer] :search_count The number of entries returned by the search.
          # @option opts [Integer] :end_of_search 0 if search continues or nonzero otherwise.
          # @option opts [Integer] :ea_error_offset should be 0 for SMB_FIND_FILE_FULL_DIRECTORY_INFO.
          # @option opts [Integer] :end_of_file The byte offset to the end of the file.
          # @option opts [Integer] :allocation_size The file allocation size in bytes.
          # @option opts [Integer] :file_attributes The extended file attributes of the file.
          # @option opts [String] :data The long name of the file.
          # @return [Integer] The number of bytes returned to the client as response.
          def send_find_full_directory_info_res(c, opts = {})
            data = opts[:data] || ''
            search_count = opts[:search_count] || 0
            end_of_search = opts[:end_of_search] || 0
            ea_error_offset = opts[:ea_error_offset] || 0
            end_of_file = opts[:end_of_file] || 0
            allocation_size = opts[:allocation_size] || 0
            file_attributes = opts[:file_attributes] || 0

            find_file = CONST::SMB_FIND_FILE_FULL_DIRECTORY_INFO_HDR.make_struct
            find_file.v['NextEntryOffset'] = CONST::SMB_FIND_FILE_FULL_DIRECTORY_INFO_HDR_LENGTH + data.length
            find_file.v['FileIndex'] = 0
            find_file.v['loCreationTime'] = lo
            find_file.v['hiCreationTime'] = hi
            find_file.v['loLastAccessTime'] = lo
            find_file.v['hiLastAccessTime'] = hi
            find_file.v['loLastWriteTime'] = lo
            find_file.v['hiLastWriteTime'] = hi
            find_file.v['loLastChangeTime'] = lo
            find_file.v['hiLastChangeTime'] = hi
            find_file.v['EndOfFile'] = end_of_file
            find_file.v['AllocationSize'] = allocation_size
            find_file.v['ExtFileAttributes'] = file_attributes
            find_file.v['FileName'] = data

            trans2_params = CONST::SMB_TRANS2_FIND_FIRST2_RES_PARAMETERS.make_struct
            trans2_params.v['SID'] = 0xfffd
            trans2_params.v['SearchCount'] = search_count
            trans2_params.v['EndOfSearch'] = end_of_search
            trans2_params.v['EaErrorOffset'] = ea_error_offset
            trans2_params.v['LastNameOffset'] = 0

            send_trans2_res(c, trans2_params, find_file)
          end
        end
      end
    end
  end
end
